(function(){
	gaf.requires('gaf.oop')
		.requires('gaf.events.Event');
    
	/**
	 * The Observable class provides the means for event subscription, unsubscription, and notification. 
	 * Objects have the ability to subscribe to events on any Observable object. When the object notifys
	 * that an event occurred all callbacks registered with that event will be notified.
	 **/
    gaf.define('gaf.Observable', null, {
        /* -------------------------
         * 	Constructor
         * ------------------------- */
        constructor: function() {
			this._subscribers = {};
        },
        /* -------------------------
         * 	Public Methods
         * ------------------------- */
        subscribe: function(type, callback){
			if ( typeof type != 'string' ) {
				throw new Error('You must provide the name of the event you are subscribing to!');
			}
			if ( typeof callback != 'function' ) {
				throw new Error('You must provide a callback function!');
			}
			// Subscribe
            if ( typeof this._subscribers[type] == 'undefined' || this._subscribers[type] == null ) {
            	this._subscribers[type] = [];
			}
			this._subscribers[type].push(callback);
				
			// Notify of the subscription
			var e = new gaf.events.Event(gaf.Observable.Events.ON_SUBSCRIBE, this, {
				type: type,
				callback: callback,
			});
			this.notify(e);
			
			return callback;
        },
        unsubscribe: function(type, callback){
			if ( typeof type != 'string' ) {
				throw new Error('You must provide the name of the event you are unsubscribing from!');
			}
			if ( typeof callback != 'function' ) {
				throw new Error('You must provide a callback function!');
			}
			// Unsubscribe
			if ( this._subscribers[type] instanceof Array ) {
			    var index = this._subscribers[type].indexOf(callback);
			    if ( index >= 0 ) {
					// Notify of the unsubscription
					var e = new gaf.events.Event(gaf.Observable.Events.ON_UNSUBSCRIBE, this, {
						type: type,
						callback: callback,
					});
					this.notify(e);
			
			        this._subscribers[type].splice(index, 1);
			    }
			}
        },
        notify: function(event){
            var eventObj;
            if ( typeof event == 'string' ) {
				eventObj = new gaf.events.Event(event, this, {});
			}
			else if ( event instanceof gaf.events.Event ) {
				if ( typeof event.type == 'undefined' || event.type == null ) {
					throw new Error('You must specify an event type!');
				}
				if ( typeof event.target == 'undefined' || event.target == null ) {
					event.target = this;
				}
				eventObj = event;
			}
			// Notify
			if ( typeof this._subscribers[eventObj.type] != 'undefined' && this._subscribers[eventObj.type] != null ) {
				var scribes = this._subscribers[eventObj.type];
				for ( var i = 0; i <= scribes.length - 1; i++ ) {
					var go = scribes[i].call(this, eventObj);
					if ( !go ) {
						break;
					}
				}
			}
        },
        /* -------------------------
         * 	Static Properties
         * ------------------------- */
		statics: {
			Events: {
				ON_SUBSCRIBE: 'onSubscribe',
				ON_UNSUBSCRIBE: 'onUnsubscribe'
			}
		}
    });
})();
